package server;

import gamemodel.GameModel;

import java.util.HashMap;

import com.google.gson.GsonBuilder;

import server.exclusionstrategy.GameExclusionStrategy;
import server.threads.GameServerIncoming;
import server.threads.GameServerOutgoing;

/**
 * Created by the LobbyServer when a user wants to create a new game.
 * Sets the creator of the GameServer as the administrator of the game
 * Maintains a list (HashMap) of players that are currently connected to the game.
 * Instantiates the GameModel which handles the logic of the game.
 * Instantiates incoming and outgoing threads for handling messages by users in the game. *
 */
public class GameServer {
	// Constant for holding the maximum number of players a GameServer can have
	public static final int MAX_PLAYERS = 4;
	
	// Constant for default time limit of a game
	public static final int DEFAULT_TIME = 5;
	
	// Constant for default score limit of a game
	public static final int DEFAULT_SCORE = 5;
	
	// The name of the GameServer
	private String name;
	
	// The number of players currently in the GameServer
	private int numPlayers;
	
	// The name of the administrator of the GameServer
	private String admin;
	
	// A HashMap of all players and their corresponding connections
	private transient HashMap<String, GameClientInfo> players;
	
	// Reference to the GameModel for making actions
	private GameModel gameModel;
	
	private transient String password;
	
	private boolean hasPassword;
	
	/**
	 * Instantiates a new GameServer
	 * @param name the name of the GameServer
	 * @param admin the name of the administrator of the GameServer
	 */
	public GameServer(String name, String admin, String password, int maxTime, int c) {
		this.name = name;
		this.numPlayers = 0;
		this.admin = admin;
		this.players = new HashMap<String, GameClientInfo>();
		
		this.gameModel = new GameModel(maxTime, maxTime);
		
		this.password = password;		
		if(password == null || password.equals("")) {
			this.hasPassword = false;
		} else {
			this.hasPassword = true;
		}
	}
	
	/**
	 * Adds a player to the GameServer
	 * @param client the client information of the player added
	 */
	public synchronized void addPlayer(GameClientInfo client) {
		// Variables for renaming a player with the same name if needed
		int i = 1;
		String newName = client.getName();
		
		// Checks if someone of the same name is already in the GameServer
		while(players.get(newName) != null) {
			// Automatically rename the player by adding an integer beside their name
			newName = new String(client.getName() + " (" + i + ")");
			i ++;
		}
		
		// Add the player to the current GameServer list of players and the GameModel
		players.put(newName, client);
		numPlayers = players.size();
		gameModel.addPlayer(newName, 0);
		
		// Creates and starts two threads for handling messages for the newly added player
		GameServerIncoming incoming = new GameServerIncoming(newName, client.getSocket(), this);
		GameServerOutgoing outgoing = new GameServerOutgoing(newName, client.getSocket(), this);
		incoming.start();
		outgoing.start();
	}
	
	/**
	 * @param name the name of the player that is to be removed from the GameServer
	 * @param retainPlayer true if the player is just leaving the game and is wanting
	 * to go back to the lobby. false if the player quit the application altogether.
	 */
	public synchronized void removePlayer(String playerName, boolean retainPlayer) {
		GameClientInfo client = players.get(playerName);
		players.remove(playerName);
		numPlayers = players.size();
		
		if(retainPlayer) {
			// Retreive a thread to handle messages between lobby and user
			LobbyServer.getInstance().retainPlayer(client);
		}
		
		// Check if the player removed is the admin. If so, remove all players
		// that are currently in the game. Assign a LobbyServerThread to them.
		// Delete the game server from the LobbyServer's list of games
		if(playerName.equals(admin)) {
			for(int i = 0; i < players.size(); i ++) {
				String notAdminName = (String) players.keySet().toArray()[i];
				client = players.get(notAdminName);
				players.remove(notAdminName);
				numPlayers = players.size();
				LobbyServer.getInstance().retainPlayer(client);
			}
			
			LobbyServer lobbyServer = LobbyServer.getInstance();
			lobbyServer.deleteGame(name);
		}
	}

	/**
	 * @return the HashMap of players currently in the GameServer
	 */
	public synchronized HashMap<String, GameClientInfo> getPlayers() {
		return players;
	}
	
	/**
	 * @return the GameModel associated with this GameServer
	 */
	public GameModel getGameModel() {
		return gameModel;
	}

	/**
	 * @return the name of the GameServer
	 */
	public String getName() {
		return name;
	}

	/**
	 * @return the name of the administrator of the GameServer
	 */
	public String getAdmin() {
		return admin;
	}
	
	public boolean hasPassword() {
		return hasPassword;
	}
	
	/**
	 * Check to see if the password string passed is equal to the password
	 * of the game
	 * 
	 * @param password that to compare with the game's password
	 * @return true if the password is correct or no password is needed.
	 * false if the password is incorrect
	 */
	public boolean checkPassword(String password) {
		if(hasPassword) {
			return this.password.equals(password);
		} else {
			return true;
		}
	}
	
	public boolean isFull() {
		return players.size() == MAX_PLAYERS;
	}
	
	public int getNumPlayers() {
		return numPlayers;
	}

	public void setNumPlayers(int numPlayers) {
		this.numPlayers = numPlayers;
	}
	
	public String toString() {
		String update = "";
		for (int i=0; i < players.size(); i++) {
			String key = (String) players.keySet().toArray()[i];
			GameClientInfo val = (GameClientInfo)players.values().toArray()[i];
			update += "[" + key + "=" + val.getSocket() + "]";

		}
	    return update;
	}

	/**
	 * @return a JSON representation of the game's state
	 */
	public String toJson() {
		GsonBuilder gson = new GsonBuilder();
		gson.setExclusionStrategies(new GameExclusionStrategy());
		return gson.create().toJson(this);
	}
}
